`timescale 1ns / 1ps

module axil_interface #(
    parameter DATA_WIDTH = 32,
    parameter ADDR_WIDTH = 16
) (
    input logic clk,
    input logic rst,

    input logic write_en,
    input logic read_en,
    input logic req_valid,

    //input logic hold_on, // busy from host

    input logic ready, // ready from host
    output logic busy,
    output logic resp_valid, //valid to host 

    input logic[ADDR_WIDTH-1:0] req_addr,
    input logic[DATA_WIDTH-1:0] req_data,

    output logic[ADDR_WIDTH-1:0] resp_addr,
    output logic[DATA_WIDTH-1:0] resp_data 
);
    parameter STRB_WIDTH = (DATA_WIDTH/8);
    // write
    logic [ADDR_WIDTH-1:0]s_axil_awaddr;   
    logic [2:0]s_axil_awprot; // none   
    logic s_axil_awvalid;  
    logic s_axil_awready;  
    logic [DATA_WIDTH-1:0]s_axil_wdata;    
    logic [STRB_WIDTH-1:0]s_axil_wstrb;    
    logic s_axil_wvalid;   
    logic s_axil_wready;   
    logic [1:0]s_axil_bresp;    
    logic s_axil_bvalid;   
    logic s_axil_bready; 

    // read
    logic [ADDR_WIDTH-1:0]s_axil_araddr;   
    logic [2:0]s_axil_arprot;   
    logic s_axil_arvalid;  
    logic s_axil_arready;  
    logic [DATA_WIDTH-1:0]s_axil_rdata;    
    logic [1:0]s_axil_rresp;    
    logic s_axil_rvalid;   
    logic s_axil_rready;

    //local
    //success to keep the read_resp data from axil_ram
    //success to finish write_req data operate
    logic success; 

    logic [DATA_WIDTH-1:0]resp_data_reg;
    logic [ADDR_WIDTH-1:0]resp_addr_reg;

    assign resp_data = resp_data_reg;
    assign resp_addr = resp_addr_reg;
    
    axil_ram #(
        .DATA_WIDTH(DATA_WIDTH),
        .ADDR_WIDTH(ADDR_WIDTH)
    ) ram0 (
        .clk(clk),
        .rst(rst),

        .s_axil_awaddr(s_axil_awaddr),
        .s_axil_awprot(s_axil_awprot),
        .s_axil_awvalid(s_axil_awvalid),
        .s_axil_awready(s_axil_awready),
        .s_axil_wdata(s_axil_wdata),
        .s_axil_wstrb(s_axil_wstrb),
        .s_axil_wvalid(s_axil_wvalid),
        .s_axil_wready(s_axil_wready),
        .s_axil_bresp(s_axil_bresp),
        .s_axil_bvalid(s_axil_bvalid),
        .s_axil_bready(s_axil_bready),

        .s_axil_araddr(s_axil_araddr),
        .s_axil_arprot(s_axil_arprot),
        .s_axil_arvalid(s_axil_arvalid),
        .s_axil_arready(s_axil_arready),
        .s_axil_rdata(s_axil_rdata),
        .s_axil_rresp(s_axil_rresp),
        .s_axil_rvalid(s_axil_rvalid),
        .s_axil_rready(s_axil_rready)
    );

    typedef enum logic[1:0] {
        IDLE  = 2'b00,
        WRITE = 2'b01,
        WAIT  = 2'b10, // wait for host
        READ  = 2'b11
    } state_t;

    state_t state, next_state;

    always @(posedge clk or negedge rst) begin
        if (rst == 1'b1) begin
            state <= IDLE;
        end else begin
            state <= next_state;
        end
    end

    always_comb begin
        case (state)
            IDLE: begin
                if(req_valid) begin
                    if (write_en && s_axil_wready && s_axil_awready) begin
                        next_state = WRITE;
                    end else if (read_en && s_axil_arready) begin
                        next_state = READ;
                    end else begin
                        next_state = IDLE;
                    end
                end else begin
                    next_state = IDLE;
                end
            end
            WRITE: begin
                if (s_axil_bvalid && s_axil_bresp == 2'b00) begin
                    next_state = WAIT;
                end else begin
                    next_state = WRITE;
                end
            end
            READ: begin
                if (s_axil_rresp == 2'b00) begin
                    next_state = WAIT;
                end else begin
                    next_state = READ;
                end
            end
            WAIT: begin
                if (success) begin
                    next_state = IDLE;
                end else begin
                    next_state = WAIT;
                end
            end
        endcase
    end

    always_ff @(posedge clk or negedge rst) begin : write_data
        if (rst) begin
            s_axil_awaddr <= 0;
            s_axil_awprot <= 3'h0;
            s_axil_awvalid <= 1'b0;

            s_axil_wdata <= 32'h0;
            s_axil_wstrb <= 4'h0;
            s_axil_wvalid <= 1'b0;

            s_axil_bready <= 1'b0;
        end else begin
            case (state)
                IDLE: begin
                    s_axil_awaddr <= 0;
                    s_axil_awprot <= 3'h0;
                    s_axil_awvalid <= 1'b0;
                    s_axil_wdata <= 32'h0;
                    s_axil_wstrb <= 4'h0;
                    s_axil_wvalid <= 1'b0;
                    s_axil_bready <= 1'b0;
                    if (write_en && req_valid) begin
                        s_axil_wdata <= req_data;
                        s_axil_awaddr <= req_addr;
                        s_axil_awvalid <= 1'b1;
                        s_axil_wstrb <= 4'hf;
                        s_axil_wvalid <= 1'b1;
                    end
                end

                WRITE: begin
                    if (write_en && req_valid) begin
                        s_axil_awvalid <= 1'b1;
                        s_axil_wstrb <= 4'hf;
                        s_axil_wvalid <= 1'b1;
                    end
                end

                WAIT: begin
                    if (ready) begin
                        s_axil_bready <= 1'b1;
                        s_axil_wvalid <= 1'b0;
                        s_axil_awvalid <= 1'b0;
                    end
                end
            endcase
        end
    end

    always_ff @(posedge clk or negedge rst) begin : read_data_from_axil_ram
        if (rst) begin
            s_axil_araddr <= 0;
            s_axil_arprot <= 3'h0;
            s_axil_arvalid <= 1'b0;
            s_axil_rready <= 1'b0;
        end else begin
            case (state)
                IDLE: begin
                    s_axil_araddr <= 0;
                    s_axil_arprot <= 3'h0;
                    s_axil_arvalid <= 1'b0;
                    s_axil_rready <= 1'b0;
                    if (read_en && req_valid) begin
                        s_axil_araddr <= req_addr;
                        s_axil_arvalid <= 1'b1;
                    end
                end
                READ: begin
                    if (read_en && req_valid) begin
                        s_axil_arvalid <= 1'b1;
                    end
                end
                WAIT: begin
                    if (ready) begin
                        s_axil_arvalid <= 1'b0;
                        s_axil_rready <= 1'b1;
                    end
                end
            endcase
        end
    end

    always_comb begin
        if (rst) begin
            success = 1'b0;
        end else begin
            success = resp_valid & ready;
        end
    end

    always_ff @(posedge clk or negedge rst) begin : for_local
        if (rst) begin
            resp_addr_reg <= 32'h0;
            resp_data_reg <= 32'h0;
            busy <= 1'b0;
            resp_valid <= 1'b0;
        end else begin
            case (state)
                IDLE: begin
                    resp_addr_reg <= resp_addr_reg;
                    resp_data_reg <= resp_data_reg;
                    busy <= 1'b0;
                    resp_valid <= 1'b0;
                end
                WRITE: begin
                    resp_addr_reg <= s_axil_awaddr;
                    resp_data_reg <= s_axil_wdata;
                    busy <= 1'b1;
                    resp_valid <= 1'b0;
                end
                READ: begin
                    resp_addr_reg <= s_axil_araddr;
                    resp_data_reg <= s_axil_rdata;
                    busy <= 1'b1;
                    resp_valid <= 1'b0;
                end
                WAIT: begin
                    resp_addr_reg <= s_axil_araddr;
                    resp_data_reg <= s_axil_rdata;
                    busy <= 1'b1;
                    resp_valid <= 1'b1;
                end
            endcase
        end
    end

endmodule

